# face_gui.py - 带注册登录功能的智能门神系统
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import cv2
import threading
import time
import os
import numpy as np
from PIL import Image, ImageTk
import webbrowser
import sqlite3
import hashlib
import re
import shutil

from config import load_config, save_config, get_user_names, add_user_name, remove_user_name
from config import set_system_running, is_system_running
from face_recognizer import FaceRecognizer
from known_person import handle_known_person
from unknown_person import handle_unknown_person, record_frame, is_recording, get_remaining_time
from face_enroll import enroll_face, train_model
from MonitoringMicrophone import Monitor
# 配置常量
CONFIG_PATH = "config.json"
DATA_PATH = "data/"
MODEL_PATH = "trainer/trainer.yml"
CASCADE_PATH = "HaarCascade/haarcascade_frontalface_default.xml"
DATABASE_PATH = "user_db.sqlite"

class LoginApp:
    def __init__(self, root):
        self.root = root
        self.root.title("智能门神系统 - 登录")
        self.root.geometry("400x300")
        self.root.configure(bg="#f0f0f0")
        self.root.resizable(False, False)
        
        # 初始化数据库
        self.init_database()
        
        # 创建登录界面
        self.create_login_ui()
        
        # 检查是否已有注册用户
        self.check_existing_users()
    
    def init_database(self):
        """初始化用户数据库"""
        self.conn = sqlite3.connect(DATABASE_PATH)
        self.cursor = self.conn.cursor()
        
        # 创建用户表
        self.cursor.execute('''
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                email TEXT UNIQUE NOT NULL,
                password TEXT NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        ''')
        self.conn.commit()
    
    def check_existing_users(self):
        """检查是否有已注册用户"""
        self.cursor.execute("SELECT COUNT(*) FROM users")
        count = self.cursor.fetchone()[0]
        
        if count == 0:
            # 没有注册用户，显示注册界面
            self.show_register()
        else:
            # 有注册用户，显示登录界面
            self.show_login()
    
    def create_login_ui(self):
        """创建登录界面框架"""
        # 主框架
        main_frame = tk.Frame(self.root, bg="#f0f0f0", padx=20, pady=20)
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 标题
        title_label = tk.Label(main_frame, text="智能门神系统", 
                              font=("Arial", 18, "bold"), 
                              fg="#2c3e50", bg="#f0f0f0")
        title_label.pack(pady=(0, 20))
        
        # 登录框架
        self.login_frame = tk.Frame(main_frame, bg="#f0f0f0")
        
        # 注册框架
        self.register_frame = tk.Frame(main_frame, bg="#f0f0f0")
        
        # 创建登录和注册表单
        self.create_login_form()
        self.create_register_form()
        
        # 初始显示登录界面
        self.show_login()
    
    def create_login_form(self):
        """创建登录表单"""
        # 邮箱标签和输入框
        email_label = tk.Label(self.login_frame, text="邮箱:", 
                              font=("Arial", 10), bg="#f0f0f0")
        email_label.grid(row=0, column=0, sticky=tk.W, pady=(0, 5))
        
        self.login_email = tk.Entry(self.login_frame, width=30, font=("Arial", 10))
        self.login_email.grid(row=0, column=1, pady=(0, 10), padx=(10, 0))
        
        # 密码标签和输入框
        password_label = tk.Label(self.login_frame, text="密码:", 
                                 font=("Arial", 10), bg="#f0f0f0")
        password_label.grid(row=1, column=0, sticky=tk.W, pady=(0, 5))
        
        self.login_password = tk.Entry(self.login_frame, width=30, 
                                      font=("Arial", 10), show="*")
        self.login_password.grid(row=1, column=1, pady=(0, 20), padx=(10, 0))
        
        # 登录按钮
        login_btn = tk.Button(self.login_frame, text="登录", 
                             command=self.login, bg="#3498db", fg="white",
                             font=("Arial", 12), width=15, pady=5)
        login_btn.grid(row=2, column=0, columnspan=2, pady=(0, 10))
        
        # 注册链接
        register_link = tk.Label(self.login_frame, text="没有账号? 点击注册", 
                                font=("Arial", 9), fg="#3498db", bg="#f0f0f0",
                                cursor="hand2")
        register_link.grid(row=3, column=0, columnspan=2)
        register_link.bind("<Button-1>", lambda e: self.show_register())
    
    def create_register_form(self):
        """创建注册表单"""
        # 邮箱标签和输入框
        email_label = tk.Label(self.register_frame, text="邮箱:", 
                              font=("Arial", 10), bg="#f0f0f0")
        email_label.grid(row=0, column=0, sticky=tk.W, pady=(0, 5))
        
        self.register_email = tk.Entry(self.register_frame, width=30, font=("Arial", 10))
        self.register_email.grid(row=0, column=1, pady=(0, 10), padx=(10, 0))
        
        # 密码标签和输入框
        password_label = tk.Label(self.register_frame, text="密码:", 
                                 font=("Arial", 10), bg="#f0f0f0")
        password_label.grid(row=1, column=0, sticky=tk.W, pady=(0, 5))
        
        self.register_password = tk.Entry(self.register_frame, width=30, 
                                         font=("Arial", 10), show="*")
        self.register_password.grid(row=1, column=1, pady=(0, 10), padx=(10, 0))
        
        # 确认密码标签和输入框
        confirm_label = tk.Label(self.register_frame, text="确认密码:", 
                                font=("Arial", 10), bg="#f0f0f0")
        confirm_label.grid(row=2, column=0, sticky=tk.W, pady=(0, 5))
        
        self.register_confirm = tk.Entry(self.register_frame, width=30, 
                                        font=("Arial", 10), show="*")
        self.register_confirm.grid(row=2, column=1, pady=(0, 20), padx=(10, 0))
        
        # 注册按钮
        register_btn = tk.Button(self.register_frame, text="注册", 
                                command=self.register, bg="#2ecc71", fg="white",
                                font=("Arial", 12), width=15, pady=5)
        register_btn.grid(row=3, column=0, columnspan=2, pady=(0, 10))
        
        # 登录链接
        login_link = tk.Label(self.register_frame, text="已有账号? 点击登录", 
                             font=("Arial", 9), fg="#3498db", bg="#f0f0f0",
                             cursor="hand2")
        login_link.grid(row=4, column=0, columnspan=2)
        login_link.bind("<Button-1>", lambda e: self.show_login())
    
    def show_login(self):
        """显示登录界面"""
        self.register_frame.pack_forget()
        self.login_frame.pack(fill=tk.BOTH, expand=True)
        self.root.title("智能门神系统 - 登录")
    
    def show_register(self):
        """显示注册界面"""
        self.login_frame.pack_forget()
        self.register_frame.pack(fill=tk.BOTH, expand=True)
        self.root.title("智能门神系统 - 注册")
    
    def hash_password(self, password):
        """对密码进行哈希处理"""
        return hashlib.sha256(password.encode()).hexdigest()
    
    def validate_email(self, email):
        """验证邮箱格式"""
        pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        return re.match(pattern, email) is not None
    
    def register(self):
        """注册新用户"""
        email = self.register_email.get().strip()
        password = self.register_password.get()
        confirm = self.register_confirm.get()
        
        # 验证输入
        if not email or not password or not confirm:
            messagebox.showerror("错误", "请填写所有字段")
            return
        
        if not self.validate_email(email):
            messagebox.showerror("错误", "请输入有效的邮箱地址")
            return
        
        if password != confirm:
            messagebox.showerror("错误", "两次输入的密码不一致")
            return
        
        if len(password) < 6:
            messagebox.showerror("错误", "密码长度至少为6位")
            return
        
        # 检查邮箱是否已存在
        self.cursor.execute("SELECT COUNT(*) FROM users WHERE email = ?", (email,))
        if self.cursor.fetchone()[0] > 0:
            messagebox.showerror("错误", "该邮箱已被注册")
            return
        
        # 插入新用户
        hashed_password = self.hash_password(password)
        try:
            self.cursor.execute("INSERT INTO users (email, password) VALUES (?, ?)", 
                               (email, hashed_password))
            self.conn.commit()
            
            # 保存邮箱到配置文件，用于邮件发送
            config = load_config()
            config['target_email'] = email
            save_config(config)
            
            messagebox.showinfo("成功", "注册成功！")
            self.show_login()
        except Exception as e:
            messagebox.showerror("错误", f"注册失败: {str(e)}")
    
    def login(self):
        """用户登录"""
        email = self.login_email.get().strip()
        password = self.login_password.get()
        
        # 验证输入
        if not email or not password:
            messagebox.showerror("错误", "请填写所有字段")
            return
        
        # 验证用户凭据
        hashed_password = self.hash_password(password)
        self.cursor.execute("SELECT id, email FROM users WHERE email = ? AND password = ?", 
                           (email, hashed_password))
        user = self.cursor.fetchone()
        
        if user:
            # 登录成功，保存用户信息并打开主应用
            self.user_id, self.user_email = user
            self.open_main_app()
        else:
            messagebox.showerror("错误", "邮箱或密码不正确")
    
    def open_main_app(self):
        """打开主应用程序"""
        self.root.destroy()  # 关闭登录窗口
        
        # 创建主应用窗口
        root = tk.Tk()
        app = FaceRecognitionApp(root, self.user_email, self.conn)
        root.protocol("WM_DELETE_WINDOW", app.on_closing)
        root.mainloop()


class FaceRecognitionApp:
    def __init__(self, root, user_email, db_connection):
        self.root = root
        self.user_email = user_email
        self.conn = db_connection
        self.cursor = self.conn.cursor()
        
        self.root.title("智能门神系统")
        self.root.geometry("1000x700")
        self.root.configure(bg="#f0f0f0")
        
        # 初始化变量
        self.face_recognizer = FaceRecognizer()
        self.is_running = False
        self.cap = None
        self.recording = False
        self.is_known_person_mode = False
        self.current_user_name = None
        self.in_conversation = False
        self.last_frame = None

        # 画面显示相关
        self.display_w = 640
        self.display_h = 480
        self.canvas_image_id = None
        self.current_image = None
        self.last_frame_time = 0

        # 创建界面
        self.create_widgets()
        
        # 加载配置
        self.config = load_config()
        
    def create_widgets(self):
        # 创建主框架
        main_frame = tk.Frame(self.root, bg="#f0f0f0")
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # 标题区域
        title_frame = tk.Frame(main_frame, bg="#f0f0f0")
        title_frame.pack(fill=tk.X, pady=(0, 10))
        
        title_label = tk.Label(title_frame, text="智能门神系统", font=("Arial", 20, "bold"), 
                              fg="#2c3e50", bg="#f0f0f0")
        title_label.pack(side=tk.LEFT)
        
        # 用户信息区域
        user_info_frame = tk.Frame(title_frame, bg="#f0f0f0")
        user_info_frame.pack(side=tk.RIGHT)
        
        user_label = tk.Label(user_info_frame, text=f"用户: {self.user_email}", 
                             font=("Arial", 10), fg="#7f8c8d", bg="#f0f0f0")
        user_label.pack(side=tk.LEFT, padx=(0, 10))
        
        # 注销按钮
        logout_btn = tk.Button(user_info_frame, text="注销", command=self.logout,
                              bg="#e74c3c", fg="white", font=("Arial", 10),
                              relief=tk.RAISED, padx=10, pady=2)
        logout_btn.pack(side=tk.LEFT)
        
        # 系统状态指示器
        status_frame = tk.Frame(title_frame, bg="#f0f0f0")
        status_frame.pack(side=tk.RIGHT, padx=(20, 0))
        
        self.status_indicator = tk.Label(status_frame, text="●", font=("Arial", 16), 
                                       fg="#e74c3c", bg="#f0f0f0")
        self.status_indicator.pack(side=tk.LEFT, padx=(0, 5))
        self.status_label = tk.Label(status_frame, text="系统未运行", font=("Arial", 12), 
                                   fg="#7f8c8d", bg="#f0f0f0")
        self.status_label.pack(side=tk.LEFT)
        
        # 按钮区域
        button_frame = tk.Frame(main_frame, bg="#f0f0f0")
        button_frame.pack(fill=tk.X, pady=(0, 10))
        
        self.enroll_btn = tk.Button(button_frame, text="录入人脸", command=self.enroll_face,
                                   bg="#3498db", fg="white", font=("Arial", 10),
                                   relief=tk.RAISED, padx=10, pady=5)
        self.enroll_btn.pack(side=tk.LEFT, padx=(0, 5))
        
        self.delete_btn = tk.Button(button_frame, text="删除人脸", command=self.delete_face,
                                   bg="#e74c3c", fg="white", font=("Arial", 10),
                                   relief=tk.RAISED, padx=10, pady=5)
        self.delete_btn.pack(side=tk.LEFT, padx=(0, 5))
        
        self.train_btn = tk.Button(button_frame, text="训练模型", command=self.train_model,
                                  bg="#9b59b6", fg="white", font=("Arial", 10),
                                  relief=tk.RAISED, padx=10, pady=5)
        self.train_btn.pack(side=tk.LEFT, padx=(0, 5))
        
        self.start_btn = tk.Button(button_frame, text="开始运行", command=self.start_recognition,
                                  bg="#27ae60", fg="white", font=("Arial", 10, "bold"),
                                  relief=tk.RAISED, padx=15, pady=5)
        self.start_btn.pack(side=tk.LEFT, padx=(0, 5))
        
        self.stop_btn = tk.Button(button_frame, text="停止运行", command=self.stop_recognition,
                                 bg="#e74c3c", fg="white", font=("Arial", 10, "bold"),
                                 relief=tk.RAISED, padx=15, pady=5, state=tk.DISABLED)
        self.stop_btn.pack(side=tk.LEFT, padx=(0, 5))
        
        self.view_recordings_btn = tk.Button(button_frame, text="查看录像", command=self.view_recordings,
                                            bg="#f39c12", fg="white", font=("Arial", 10),
                                            relief=tk.RAISED, padx=10, pady=5)
        self.view_recordings_btn.pack(side=tk.LEFT)
        
        # 主要内容区域
        content_frame = tk.Frame(main_frame, bg="#f0f0f0")
        content_frame.pack(fill=tk.BOTH, expand=True)
        
        # 左侧用户列表
        user_frame = tk.LabelFrame(content_frame, text="已注册用户", font=("Arial", 12, "bold"),
                                  bg="#f0f0f0", padx=10, pady=10)
        user_frame.pack(side=tk.LEFT, fill=tk.BOTH, padx=(0, 10))
        
        # 用户列表框
        list_frame = tk.Frame(user_frame, bg="#f0f0f0")
        list_frame.pack(fill=tk.BOTH, expand=True)
        
        self.user_listbox = tk.Listbox(list_frame, font=("Arial", 11), 
                                     selectbackground="#3498db", selectforeground="white")
        self.user_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.user_listbox.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.user_listbox.configure(yscrollcommand=scrollbar.set)
        
        # 右侧视频区域
        video_frame = tk.LabelFrame(content_frame, text="实时监控", font=("Arial", 12, "bold"),
                                   bg="#f0f0f0", padx=10, pady=10)
        video_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
        
        # 视频显示区域
        video_display_frame = tk.Frame(video_frame, bg="#000000")
        video_display_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
        
        self.video_canvas = tk.Canvas(video_display_frame, bg="#000000", highlightthickness=0)
        self.video_canvas.pack(fill=tk.BOTH, expand=True)
        
        # 视频状态信息
        video_status_frame = tk.Frame(video_frame, bg="#f0f0f0")
        video_status_frame.pack(fill=tk.X)
        
        # 录像状态
        recording_frame = tk.Frame(video_status_frame, bg="#f0f0f0")
        recording_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)
        
        tk.Label(recording_frame, text="录像状态:", font=("Arial", 10), 
                fg="#2c3e50", bg="#f0f0f0").pack(side=tk.LEFT)
        self.recording_status = tk.Label(recording_frame, text="未录像", font=("Arial", 10, "bold"),
                                        fg="#e74c3c", bg="#f0f0f0")
        self.recording_status.pack(side=tk.LEFT, padx=(5, 0))
        
        # 倒计时
        countdown_frame = tk.Frame(video_status_frame, bg="#f0f0f0")
        countdown_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)
        
        tk.Label(countdown_frame, text="倒计时:", font=("Arial", 10), 
                fg="#2c3e50", bg="#f0f0f0").pack(side=tk.LEFT)
        self.countdown_label = tk.Label(countdown_frame, text="0秒", font=("Arial", 10, "bold"),
                                       fg="#f39c12", bg="#f0f0f0")
        self.countdown_label.pack(side=tk.LEFT, padx=(5, 0))
        
        # 对话状态
        conversation_frame = tk.Frame(video_status_frame, bg="#f0f0f0")
        conversation_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)
        
        tk.Label(conversation_frame, text="对话状态:", font=("Arial", 10), 
                fg="#2c3e50", bg="#f0f0f0").pack(side=tk.LEFT)
        self.conversation_status = tk.Label(conversation_frame, text="未对话", font=("Arial", 10, "bold"),
                                          fg="#3498db", bg="#f0f0f0")
        self.conversation_status.pack(side=tk.LEFT, padx=(5, 0))
        
        # 系统状态栏
        system_status_frame = tk.Frame(main_frame, bg="#dfe6e9", height=30)
        system_status_frame.pack(fill=tk.X, side=tk.BOTTOM, pady=(10, 0))
        system_status_frame.pack_propagate(False)  # 防止内容改变框架大小
        
        self.system_status_label = tk.Label(system_status_frame, text="系统就绪", font=("Arial", 10),
                                          fg="#2c3e50", bg="#dfe6e9")
        self.system_status_label.pack(side=tk.LEFT, padx=10)
        
        # 绑定事件
        self.video_canvas.bind("<Configure>", self.on_canvas_resize)
        
        # 更新用户列表
        self.update_user_list()
    
    def logout(self):
        """注销当前用户"""
        if messagebox.askyesno("确认", "确定要注销吗？这将删除所有用户数据并退出系统。"):
            # 删除数据库中的所有用户
            self.cursor.execute("DELETE FROM users")
            self.conn.commit()
            self.conn.close()
            # 清除配置文件中的用户数据和目标邮箱
            config = load_config()
            config['users'] = []  # 清空用户列表
            config['next_id'] = 0  # 重置ID计数器
            config['target_email'] = ""  # 清空目标邮箱
            save_config(config)
                    # 删除人脸数据文件夹中的所有内容
            data_dir = "data/"
            if os.path.exists(data_dir):
                shutil.rmtree(data_dir)
                os.makedirs(data_dir)  # 重新创建空目录
            # 关闭应用程序
            self.on_closing()
            
            # 重新启动登录界面
        root = tk.Tk()
        app = LoginApp(root)
        root.mainloop()
    
    def on_canvas_resize(self, event):
        """当Canvas大小改变时，重新绘制当前帧"""
        if self.last_frame is not None:
            self._update_canvas(self.last_frame)
    
    def update_user_list(self):
        """更新用户列表"""
        self.user_listbox.delete(0, tk.END)
        users = get_user_names()
        for user in users:
            self.user_listbox.insert(tk.END, user)
    
    # -------------------- 人脸录入、删除、训练 --------------------
    def enroll_face(self):
        """录入人脸"""
        user_name = simpledialog.askstring("录入人脸", "请输入用户名:")
        if not user_name:
            return
        if user_name in get_user_names():
            messagebox.showwarning("警告", f"用户 '{user_name}' 已存在!")
            return
        threading.Thread(target=self._enroll_face_thread, args=(user_name,), daemon=True).start()
    
    def _enroll_face_thread(self, user_name):
        self.system_status_label.config(text=f"正在为 {user_name} 录入人脸...")
        self.status_indicator.config(fg="#f39c12")
        self.enroll_btn.config(state=tk.DISABLED)
        count = enroll_face(user_name)
        self.root.after(0, lambda: self.system_status_label.config(text=f"成功为 {user_name} 采集了 {count} 张面部样本"))
        self.root.after(0, self.update_user_list)
        self.root.after(0, lambda: self.enroll_btn.config(state=tk.NORMAL))
        self.root.after(0, lambda: self.status_indicator.config(fg="#2ecc71"))
        if count > 0:
            self.train_model()
    
    def delete_face(self):
        selected = self.user_listbox.curselection()
        if not selected:
            messagebox.showwarning("警告", "请先选择一个用户!")
            return
        user_name = self.user_listbox.get(selected[0])
        if messagebox.askyesno("确认", f"确定要删除用户 '{user_name}' 吗?"):
            if remove_user_name(user_name):
                import shutil
                user_data_dir = os.path.join(DATA_PATH, user_name)
                if os.path.exists(user_data_dir):
                    shutil.rmtree(user_data_dir)
                self.update_user_list()
                messagebox.showinfo("成功", f"用户 '{user_name}' 已删除")
                self.train_model()
            else:
                messagebox.showerror("错误", f"删除用户 '{user_name}' 失败")
    
    def train_model(self):
        self.system_status_label.config(text="正在训练模型...")
        self.status_indicator.config(fg="#f39c12")
        self.train_btn.config(state=tk.DISABLED)
        threading.Thread(target=self._train_model_thread, daemon=True).start()
    
    def _train_model_thread(self):
        success, sample_count = train_model()
        if success:
            self.root.after(0, lambda: self.system_status_label.config(text=f"训练完成! 共使用了 {sample_count} 张图像"))
            self.root.after(0, lambda: self.status_indicator.config(fg="#2ecc71"))
        else:
            self.root.after(0, lambda: self.system_status_label.config(text="训练失败，没有找到训练数据!"))
            self.root.after(0, lambda: self.status_indicator.config(fg="#e74c3c"))
        self.root.after(0, lambda: self.train_btn.config(state=tk.NORMAL))
    
    # -------------------- 录像查看 --------------------
    def view_recordings(self):
        recordings_dir = "recordings"
        if os.path.exists(recordings_dir):
            webbrowser.open(os.path.abspath(recordings_dir))
        else:
            messagebox.showinfo("信息", "还没有录制任何视频")
    
    # -------------------- 对话 --------------------
    def start_conversation(self, user_name):
        self.in_conversation = True
        self.root.after(0, lambda: self.system_status_label.config(text=f"与 {user_name} 开始对话..."))
        self.root.after(0, lambda: self.status_label.config(text="对话中"))
        self.root.after(0, lambda: self.status_indicator.config(fg="#3498db"))
        self.root.after(0, lambda: self.conversation_status.config(text="对话中"))
        threading.Thread(target=self._conversation_thread, args=(user_name,), daemon=True).start()
    
    def _conversation_thread(self, user_name):
        try:
            Monitor()
        except Exception as e:
            print(f"对话过程中出错: {e}")
        finally:
            self.in_conversation = False
            self.root.after(0, lambda: self.system_status_label.config(text="对话结束，继续识别"))
            self.root.after(0, lambda: self.status_label.config(text="系统运行中"))
            self.root.after(0, lambda: self.status_indicator.config(fg="#2ecc71"))
            self.root.after(0, lambda: self.conversation_status.config(text="未对话"))
    
    # -------------------- 人脸识别 --------------------
    def start_recognition(self):
        if self.is_running:
            return
        set_system_running(True)
        self.is_running = True
        self.start_btn.config(state=tk.DISABLED)
        self.stop_btn.config(state=tk.NORMAL)
        self.system_status_label.config(text="正在运行人脸识别...")
        self.status_label.config(text="系统运行中")
        self.status_indicator.config(fg="#2ecc71")
        from server_a import servera
            # 启动 Flask 服务器
        try:
            # 导入 server 模块
            # 在新线程中启动服务器
            server_thread = threading.Thread(target=servera, daemon=True)
            server_thread.start()
            self.system_status_label.config(text="服务器已启动，请访问 http://服务器IP:5000")
        except Exception as e:
            print(f"启动服务器时出错: {e}")
            self.system_status_label.config(text="服务器启动失败")
        self.cap = cv2.VideoCapture(0)
        if not self.cap.isOpened():
            messagebox.showerror("错误", "无法打开摄像头")
            self.stop_recognition()
            return

        # 约束相机输出分辨率为显示区域大小，减少裁剪和缩放开销
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.display_w)
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.display_h)

        threading.Thread(target=self._recognition_thread, daemon=True).start()
    
    def _recognition_thread(self):
        is_known_person = False
        current_user_name = None
        recognition_count = 0
        recognition_results = {}
        
        while self.is_running:
            if self.in_conversation:
                ret, frame = self.cap.read()
                if ret:
                    self.last_frame = frame.copy()
                    self._update_canvas(frame)
                time.sleep(0.01)
                continue
            
            ret, frame = self.cap.read()
            if not ret:
                break
            
            # 保存最后一帧用于重绘
            self.last_frame = frame.copy()
            
            processed_frame, face_locations, face_names, confidence_values = self.face_recognizer.process_frame(frame)
            
            # 录像状态
            if is_recording():
                self.root.after(0, lambda: self.recording_status.config(text="正在录像中"))
                self.root.after(0, lambda: self.countdown_label.config(text=""))
            else:
                remaining_time = get_remaining_time()
                if remaining_time > 0:
                    self.root.after(0, lambda: self.recording_status.config(text="检测到陌生人"))
                    self.root.after(0, lambda: self.countdown_label.config(text=f"{remaining_time:.1f}秒"))
                else:
                    self.root.after(0, lambda: self.recording_status.config(text="未录像"))
                    self.root.after(0, lambda: self.countdown_label.config(text="0秒"))
            
            # 识别统计
            if recognition_count < 10:
                recognition_count += 1
                for name in face_names:
                    recognition_results[name] = recognition_results.get(name, 0) + 1
                self._update_canvas(processed_frame)
                time.sleep(0.001)
                continue
            
            final_name = max(recognition_results, key=recognition_results.get) if recognition_results else "unknown"
            recognition_count, recognition_results = 0, {}
            
            if final_name != "unknown" and not is_known_person:
                is_known_person, current_user_name = True, final_name
                self.root.after(0, lambda: self.system_status_label.config(text=f"检测到已知人员: {current_user_name}"))
                handle_known_person(current_user_name)
                self.start_conversation(current_user_name)
                self.is_known_person_mode, self.current_user_name = True, current_user_name
            
            elif final_name == "unknown" and is_known_person:
                is_known_person, current_user_name = False, None
                self.root.after(0, lambda: self.system_status_label.config(text="已知人员已离开，返回监控模式"))
                self.is_known_person_mode, self.current_user_name = False, None
            
            if not is_known_person:
                frame_width, frame_height = int(self.cap.get(3)), int(self.cap.get(4))
                processed_frame = handle_unknown_person(processed_frame, face_locations, face_names, frame_width, frame_height)
                record_frame(processed_frame)
            
            self._update_canvas(processed_frame)
            time.sleep(0.001)
    
    def _update_canvas(self, frame):
        """线程安全地更新画面：根据 Canvas 实际大小等比缩放，居中显示"""
        if frame is None:
            return

        # 获取 Canvas 实际尺寸
        canvas_w = self.video_canvas.winfo_width()
        canvas_h = self.video_canvas.winfo_height()
        if canvas_w <= 1 or canvas_h <= 1:
            # Canvas 还没初始化完成，跳过
            self.root.after(50, lambda: self._update_canvas(frame))
            return

        # 按 Canvas 大小等比缩放，保持原始宽高比
        h, w = frame.shape[:2]
        if w == 0 or h == 0:
            return

        # 计算缩放比例，保持原始宽高比
        scale = min(canvas_w / w, canvas_h / h)
        new_w = max(1, int(w * scale))
        new_h = max(1, int(h * scale))
        resized = cv2.resize(frame, (new_w, new_h), interpolation=cv2.INTER_LINEAR)

        # 创建背景图像，将缩放后的图像居中放置
        bg_image = np.zeros((canvas_h, canvas_w, 3), dtype=np.uint8)
        
        # 计算居中位置
        x_offset = (canvas_w - new_w) // 2
        y_offset = (canvas_h - new_h) // 2
        
        # 将缩放后的图像放置到背景中央
        bg_image[y_offset:y_offset+new_h, x_offset:x_offset+new_w] = resized

        # BGR -> RGB
        img_rgb = cv2.cvtColor(bg_image, cv2.COLOR_BGR2RGB)
        img_pil = Image.fromarray(img_rgb)
        img_tk = ImageTk.PhotoImage(image=img_pil)

        def _draw():
            if self.canvas_image_id is None:
                # 第一次创建图像项
                self.canvas_image_id = self.video_canvas.create_image(0, 0, anchor=tk.NW, image=img_tk)
            else:
                # 更新图像内容
                self.video_canvas.itemconfig(self.canvas_image_id, image=img_tk)

            # 持有引用避免被回收
            self.current_image = img_tk

        # 在主线程安全执行
        self.root.after_idle(_draw)

    
    def stop_recognition(self):
        set_system_running(False)
        self.in_conversation, self.is_running = False, False
        self.start_btn.config(state=tk.NORMAL)
        self.stop_btn.config(state=tk.DISABLED)
        self.system_status_label.config(text="系统已停止")
        self.status_label.config(text="系统未运行")
        self.status_indicator.config(fg="#e74c3c")
        self.recording_status.config(text="未录像")
        self.countdown_label.config(text="0秒")
        self.conversation_status.config(text="未对话")
        if self.cap:
            self.cap.release()
            self.cap = None
    
    def on_closing(self):
        self.stop_recognition()
        if hasattr(self, 'conn'):
            self.conn.close()
        self.root.destroy()


if __name__ == "__main__":
    # 启动登录界面
    root = tk.Tk()
    app = LoginApp(root)
    root.mainloop()